package test.net.shambolica.jport.mock;

import net.erl.objects.ErlTerm;
import static net.erl.objects.pattern.EP.ptuple;
import static net.erl.objects.pattern.EP.pvar;
import static net.erl.objects.ET.tuple;
import static net.erl.objects.ET.atom;
import static net.erl.objects.ET.number;
import net.shambolica.jport.runner.EtfStreamConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.ProtocolException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

/**
 * An object used as a mock master.
 * Parameters:
 * - Listener socket.
 * - Cookie.
 * - Expected reply.
 * - A list of terms to send.
 * It opens a thread that:
 * - Accepts on a predefined socket, yielding an EtfStreamConnection.
 * - Expects a cookie, sends expected reply.
 * - Sends the list of terms.
 * - Waits for one last term.
 *
 * The result is returned in a future (OK|ERROR).
 */
public class MockMaster {

    private static Logger log = LoggerFactory.getLogger(MockMaster.class);


    public static class CompletionStatus {
        public final boolean isOk;
        public final Exception errorException;
        public final String errorMessage;

        public CompletionStatus(boolean ok, Exception errorException, String errorMessage) {
            isOk = ok;
            this.errorException = errorException;
            this.errorMessage = errorMessage;
        }

    }

    public static FutureTask<CompletionStatus> run(final ServerSocket listener,
                                                   final ErlTerm cookie,
                                                   final ErlTerm expected,
                                                   final List<ErlTerm> termsToSend,
                                                   final ErlTerm done) {

        final CompletionStatus[] status = new CompletionStatus[1];
        final FutureTask<CompletionStatus> ret = new FutureTask<CompletionStatus>(
                new Callable<CompletionStatus>() {
                    public CompletionStatus call() throws Exception {
                        return status[0];
                    }
                }
        );

        final Thread thread = new Thread(new Runnable() {
            public void run() {

                String msg = null;
                Exception ex = null;

                try {

                    Socket sock = listener.accept(); // leaks...
                    log.info("accepted: {}", sock);

                    EtfStreamConnection conn = new EtfStreamConnection(sock.getInputStream(), sock.getOutputStream());
                    log.info("got etf conn");

                    if (cookie != null) {
                        log.info("waiting for cookie: {}", cookie);
                        ErlTerm cookieTerm = conn.readTerm();
                        log.info("got cookie term: {}", cookieTerm);
                        if (! cookie.equals(cookieTerm)) {
                            log.error("cookie term does not match");
                            throw new ProtocolException("cookie mismatch");
                        }
                    }

                    if (expected != null) {
                        log.info("sending expected: {}", expected);
                        conn.writeTerm(expected);
                    }

                    if (termsToSend != null && ! termsToSend.isEmpty()) {
                        log.info("sending terms");
                        for (ErlTerm it: termsToSend) {
                            log.info("sending: {}", it);
                            conn.writeTerm(it);
                        }
                    }

                    if (done != null) {
                        log.info("waiting for done term: {}", done);
                        ErlTerm doneTerm = conn.readTerm();
                        log.info("got: {}", doneTerm);
                        if (! doneTerm.equals(done)) {
                            log.error("done term mismatch");
                            throw new ProtocolException("done term mismatch");
                        }
                    }


                    // And finally, return the status:
                    log.info("success");
                    status[0] = new CompletionStatus(true, null, "completed ok");
                    ret.run();

                } catch (IOException e) {
                    ex = e;
                    log.error("io exception: {}", e.getMessage());
                } catch (InterruptedException e) {
                    ex = e;
                    log.error("interrupted: {}", e.getMessage());
                } catch (Exception e) {
                    ex = e;
                    log.error("unexpected exception: {}: {}", e.getClass().getName(), e.getMessage());
                } finally {
                    status[0] = new CompletionStatus(false, ex, msg);
                    ret.run();
                }
            }
        });

        // We kick-start the thread, and get out of here.
        thread.start();
        return ret;

    }

}
